package com.example.featuresdk.repository

import android.util.Log
import androidx.lifecycle.*
import com.example.featuresdk.vo.Resource
import com.example.featuresdk.vo.Status
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import retrofit2.Call

class ReadAsLiveData<T>(
    val fetchLocalData:()->LiveData<T>,
    val shouldFetchRemoteData: (localData:T?)->Boolean,
    val fetchRemoteData:() -> Call<Resource<T>>,
    val onStatusChanged: (Status) -> Unit,
    val saveRemoteData: suspend (data: T) ->Unit
){

    private val TAG = "RepositoryReadOperation"

    suspend fun execute(scope: CoroutineScope): LiveData<T> {

        //Create Observer which will be triggered when data is collected from Room
        val stopObservingRoomData = MutableLiveData(false)
        val localDataObserver = Observer<T>(){ localData ->
            //1.Stop observing after processing room data
            stopObservingRoomData.value = true

            //2.Check the local data to see whether it is required to fetch data from network
            if(shouldFetchRemoteData(localData)) {
                Log.i(TAG, "fetching data from network")
                scope.launch {
                    synchronizeRemoteData()
                }
            }else{
                Log.i(TAG, "No need to fetch data from network")
                onStatusChanged(Status.Success)
            }
        }

        return withContext(scope.coroutineContext){
            val roomLiveData = fetchLocalData()

            //Add observer to process local room data, e.g. synchronize remote data to room database
            roomLiveData.observeForever(localDataObserver)

            //Remove observer after processing local room data
            // stopObservingRoomData will be updated to "true" when local room data is obtained
            // and its callback onChanged(true) will be triggered to remove localDataObserver from roomLiveData
            stopObservingRoomData.observeForever {
                if (it) {
                    roomLiveData.removeObserver(localDataObserver)
                }
            }

            return@withContext roomLiveData
        }
    }

    private suspend fun synchronizeRemoteData(){

        runCatching {
            fetchRemoteData().execute()
        }.onSuccess { response ->
            //process the remote data on successful network response
            Log.i(TAG, "response.isSuccessful = ${response.isSuccessful}")

            if (!response.isSuccessful) {//Returns true if code() is in the range [200..300).
                Log.i(TAG, "Error:(${response.message()}):${response.errorBody()}}")
                onStatusChanged(Status.Error)
                return@onSuccess
            }

            response.body().let {
                if (it?.data == null) {
                    Log.i(TAG, "response.body() without data, message = ${it?.message}")
                    onStatusChanged(Status.Empty)
                } else {
                    Log.i(TAG, "save data to room")
                    saveRemoteData(it.data)
                    onStatusChanged(Status.Success)
                }
            }
        }.onFailure {
            //process network exception, e.g. invalid json error
            onStatusChanged(Status.Error)
            Log.i(TAG, "Failed to fetch remote data. Error: ${it.toString()}")
        }
    }
}